package beautiful.butterfly.schedule.strategy;

import beautiful.butterfly.schedule.Addresss;
import beautiful.butterfly.schedule.ScheduleContext;
import beautiful.butterfly.schedule.Schedules;
import beautiful.butterfly.schedule.TaskDeal;
import beautiful.butterfly.schedule.task_manager.IDataManager;
import beautiful.butterfly.schedule.task_manager.ScheduleManager;
import beautiful.butterfly.schedule.zookeeper.DataManager;
import beautiful.butterfly.schedule.zookeeper.Manager;
import beautiful.butterfly.schedule.zookeeper.ZooKeeperManager;
import lombok.Getter;
import lombok.Setter;
import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class ScheduleFactory implements ApplicationContextAware {

    protected static transient Logger logger = LoggerFactory.getLogger(ScheduleFactory.class);
    @Setter
    @Getter
    public boolean start = true;
    /**
     * MachineInfoTimerTask
     * zookeeper  task
     * jmx tbschedule
     */
    @Setter
    @Getter
    public volatile long timerTaskHeartBeatTS = System.currentTimeMillis();
    @Setter
    @Getter
    protected ZooKeeperManager zooKeeperManager;
    protected Lock lock = new ReentrantLock();
    @Setter
    @Getter
    volatile String errorMessage = "No config Zookeeper connect infomation";
    @Setter
    @Getter
    private Map<String, String> zkConfig;
    @Setter
    @Getter
    private int timerInterval = 2000;
    @Setter
    @Getter
    private IDataManager dataManager;
    @Setter
    @Getter
    private Manager manager;
    @Setter
    @Getter
    private Map<String, List<IStrategyTask>> strategyNameToStrategyTaskListMap = new ConcurrentHashMap<String, List<IStrategyTask>>();
    @Setter
    @Getter
    private ApplicationContext applicationcontext;
    @Setter
    @Getter
    private String machineId;
    @Setter
    @Getter
    private String ip;
    @Setter
    @Getter
    private String hostName;
    @Setter
    @Getter
    private Timer timer;
    @Setter
    @Getter
    private MachineInfoTimerTask machineInfoTimerTask;
    @Setter
    @Getter
    private InitThread initThread;

    public ScheduleFactory() {
        this.ip = Addresss.getLocalHostAddress();
        this.hostName = Addresss.getLocalHostName();
    }

    public void init() throws Exception {
        Properties properties = new Properties();
        for (Map.Entry<String, String> entry : this.zkConfig.entrySet()) {
            properties.put(entry.getKey(), entry.getValue());
        }
        this.init(properties);
    }

    public void reInit(Properties properties) throws Exception {
        if (this.start == true || this.timer != null || this.strategyNameToStrategyTaskListMap.size() > 0) {
            throw new Exception("");
        }
        this.init(properties);
    }

    /**
     * 调度器入口
     */
    public void init(Properties properties) throws Exception {
        if (this.initThread != null) {
            this.initThread.stopThread();
        }
        //
        this.lock.lock();
        try {
            this.dataManager = null;//data
            this.manager = null;//strategy
            if (this.zooKeeperManager != null) {
                this.zooKeeperManager.close();
            }
            //
            ScheduleContext.setScheduleFactory(this);
            this.zooKeeperManager = new ZooKeeperManager(properties);
            this.errorMessage = "Zookeeper connecting ......" + this.zooKeeperManager.getZooKeeperConnectString();
            initThread = new InitThread(this);
            initThread.setName("ScheduleFactory-initThread");
            initThread.start();
        } finally {
            this.lock.unlock();
        }
    }

    public void doInit() throws Exception {
        this.zooKeeperManager.doInit();
        //
        this.dataManager = new DataManager(this.zooKeeperManager);
        this.manager = new Manager(this.zooKeeperManager);
        if (this.start == true) {
            this.manager.registerMachineInfo(this);
            if (this.timer == null) {
                this.timer = new Timer("ScheduleFactory-Timer");
            }
            if (this.machineInfoTimerTask == null) {
                this.machineInfoTimerTask = new MachineInfoTimerTask(this);
                this.timer.schedule(this.machineInfoTimerTask, 2000, this.timerInterval);
            }
        }
    }


    public IStrategyTask createStrategyTask(Strategy strategy)
            throws Exception {
        IStrategyTask strategyTask = null;
        try {
            if (Kind.Schedule == strategy.getKind()) {
                String baseTaskType = Schedules.getBaseTaskType(strategy.getTaskName());
                String ownSign = Schedules.getOwnSign(strategy.getTaskName());
                strategyTask = new ScheduleManager(this, baseTaskType, ownSign, dataManager);//Schedule
            } else if (Kind.JavaClass == strategy.getKind()) {
                strategyTask = (IStrategyTask) Class.forName(strategy.getTaskName()).newInstance();//IStrategyTask
                strategyTask.init(strategy.getStrategyName(), strategy.getTaskParameter());
            } else if (Kind.SpringBean == strategy.getKind()) {
                strategyTask = (IStrategyTask) this.getBean(strategy.getTaskName());
                strategyTask.init(strategy.getStrategyName(), strategy.getTaskParameter());
            }
        } catch (Exception e) {
            logger.error("strategy java or bean ,schedule" + strategy.getStrategyName(), e);
        }
        return strategyTask;
    }

    public void refresh() throws Exception {
        this.lock.lock();
        try {
            MachineInfo machineInfo = null;
            boolean isException = false;
            try {
                machineInfo = this.getManager().loadMachineInfoInfo(this.getMachineId());
            } catch (Exception e) {
                isException = true;
                logger.error("serverId=" + this.getMachineId(), e);
            }
            if (isException == true) {
                try {
                    stopStrategyTaskList(null);// 停止所有的调度任务
                    this.getManager().unRregisterMachineInfo(this);
                } finally {
                    rebuildStrategyTask();
                }
            } else if (machineInfo.isStart() == false) {
                stopStrategyTaskList(null);// 停止所有的调度任务
                this.getManager().unRregisterMachineInfo(this);
            } else {
                rebuildStrategyTask();
            }
        } finally {
            this.lock.unlock();
        }
    }

    public void rebuildStrategyTask() throws Exception {
        List<String> strategyNameList = this.getManager().registerMachineInfo(this);
        for (String strategyName : strategyNameList) {
            this.stopStrategyTaskList(strategyName);
        }
        this.updateRunntimeStrategyReqestNum();
        this.rebuildStrategyTaskList();
    }


    public void assignScheduleServer() throws Exception {
        for (RunntimeStrategy runntimeStrategy : this.manager.loadRunntimeStrategyListByMachineId(this.machineId)) {
            List<RunntimeStrategy> runntimeStrategyList = this.manager.loadRunntimeStrategyByStrategyName(runntimeStrategy.getStrategyName());
            if (runntimeStrategyList.size() == 0 || this.isLeader(this.machineId, runntimeStrategyList) == false) {
                continue;
            }
            //中心服务器进行分配

            Strategy strategy = this.manager.loadStrategy(runntimeStrategy.getStrategyName());
            //int taskItemNum, int serverNum
            int[] taskNumbers = Schedules.allot(strategy.getAssignNumber(), runntimeStrategyList.size());
            for (int i = 0; i < runntimeStrategyList.size(); i++) {
                this.manager.updateRunntimeStrategyReqestNum(runntimeStrategy.getStrategyName(), runntimeStrategyList.get(i).getMachineId(), taskNumbers[i]);
            }
        }
    }

    public boolean isLeader(String uuid, List<RunntimeStrategy> runntimeStrategyList) {
        try {
            long num = Long.parseLong(uuid.substring(uuid.lastIndexOf("$") + 1));
            for (RunntimeStrategy runntimeStrategy : runntimeStrategyList) {
                if (num > Long.parseLong(runntimeStrategy.getMachineId().substring(
                        runntimeStrategy.getMachineId().lastIndexOf("$") + 1))) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            logger.error("" + uuid, e);
            return true;
        }
    }

    public void reInitStrategyTask() throws Exception {
        for (RunntimeStrategy runntimeStrategy : this.manager.loadRunntimeStrategyListByMachineId(this.machineId)) {


            String strategyName = runntimeStrategy.getStrategyName();
            List<IStrategyTask> strategyTaskList = this.strategyNameToStrategyTaskListMap.get(strategyName);
            if (strategyTaskList == null) {
                strategyTaskList = new ArrayList<IStrategyTask>();
                this.strategyNameToStrategyTaskListMap.put(strategyName, strategyTaskList);
            }
            //先减
            while (strategyTaskList.size() > 0 && strategyTaskList.size() > runntimeStrategy.getRequestNum()) {
                IStrategyTask strategyTask = strategyTaskList.remove(strategyTaskList.size() - 1);
                try {
                    strategyTask.stop(strategyName);
                } catch (Throwable e) {
                    logger.error("StrategyName:" + strategyName, e);
                }
            }
            //后加
            Strategy strategy = this.manager.loadStrategy(strategyName);
            while (strategyTaskList.size() < runntimeStrategy.getRequestNum()) {
                IStrategyTask strategyTask = this.createStrategyTask(strategy);
                if (null == strategyTask) {
                    logger.error("strategy name=" + strategy.getStrategyName());
                }
                strategyTaskList.add(strategyTask);
            }
        }
    }


    public void stopStrategyTaskList(String strategyName) throws Exception {
        if (strategyName == null) {
            String[] strategyNameList = this.strategyNameToStrategyTaskListMap.keySet().toArray(new String[0]);
            for (String $strategyName : strategyNameList) {
                for (IStrategyTask strategyTask : this.strategyNameToStrategyTaskListMap.get($strategyName)) {
                    try {
                        strategyTask.stop(strategyName);
                    } catch (Throwable e) {
                        logger.error("strategyName=" + strategyName, e);
                    }
                }
                this.strategyNameToStrategyTaskListMap.remove($strategyName);
            }
        } else {
            List<IStrategyTask> strategyTaskList = this.strategyNameToStrategyTaskListMap.get(strategyName);
            if (strategyTaskList != null) {
                for (IStrategyTask strategyTask : strategyTaskList) {
                    try {
                        strategyTask.stop(strategyName);
                    } catch (Throwable e) {
                        logger.error("strategyName=" + strategyName, e);
                    }
                }
                this.strategyNameToStrategyTaskListMap.remove(strategyName);
            }

        }
    }


    public void stopAll() throws Exception {
        try {
            lock.lock();
            this.start = false;
            if (this.initThread != null) {
                this.initThread.stopThread();
            }
            if (this.timer != null) {
                if (this.machineInfoTimerTask != null) {
                    this.machineInfoTimerTask.cancel();
                    this.machineInfoTimerTask = null;
                }
                this.timer.cancel();
                this.timer = null;
            }
            this.stopStrategyTaskList(null);
            if (this.zooKeeperManager != null) {
                this.zooKeeperManager.close();
            }
            if (this.manager != null) {
                try {
                    ZooKeeper zooKeeper = this.manager.getZooKeeper();
                    if (zooKeeper != null) {
                        zooKeeper.close();
                    }
                } catch (Exception e) {
                    logger.error("stopAll zookeeper getZooKeeper", e);
                }
            }
            this.machineId = null;
            logger.info("stopAll");
        } catch (Throwable e) {
            logger.error("stopAll" + e.getMessage(), e);
        } finally {
            lock.unlock();
        }
    }


    public void reStart() throws Exception {
        try {
            if (this.timer != null) {
                if (this.machineInfoTimerTask != null) {
                    this.machineInfoTimerTask.cancel();
                    this.machineInfoTimerTask = null;
                }
                this.timer.purge();
            }
            //
            this.stopStrategyTaskList(null);
            //
            if (this.zooKeeperManager != null) {
                this.zooKeeperManager.close();
            }
            this.machineId = null;
            //
            this.init();
        } catch (Throwable e) {
            logger.error("" + e.getMessage(), e);
        }
    }

    public boolean isZookeeperInitialSucess() throws Exception {
        return this.zooKeeperManager.zooKeeperIsConnected();
    }

    public String[] getScheduleTaskDealList() {
        return applicationcontext.getBeanNamesForType(TaskDeal.class);

    }

    public IDataManager getDataManager() {
        if (this.dataManager == null) {
            throw new RuntimeException(this.errorMessage);
        }
        return dataManager;
    }

    public Manager getManager() {
        if (this.manager == null) {
            throw new RuntimeException(this.errorMessage);
        }
        return manager;
    }


    public Object getBean(String beanName) {
        return applicationcontext.getBean(beanName);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationcontext = applicationContext;
    }
}

